/* 
Simple XML Parser for GWT
Copyright (C) 2006 musachy http://gwt.components.googlepages.com/
Copyright (C) 2006 Gettysburg College http://www.gettysburg.edu/

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA 
*/
package com.iisageek.client.widgets.xml;

import java.util.Stack;
public abstract class Node {
    //PARSING
    private static String resolveEntities(String in) {
	String [] pieces = in.split("&");
	String out=pieces[0];
	for(int i=1;i<pieces.length;i++) {
	    if(pieces[i].equals(""))
		continue;
	    String [] entityTextPair = pieces[i].split(";");
	    if(entityTextPair.length==1) {
		out+="&"+pieces[i];
		continue;
	    }
	    out+=resolveEntity(entityTextPair[0]);
	    for(int j=1;j<entityTextPair.length;j++)
		out+=entityTextPair[j];
	}
	return out;
    }

    private static String resolveEntity(String ent) {
	if(ent.equals("amp"))
	    return "&";
	else if(ent.equals("quot"))
	    return "\"";
	else if(ent.equals("gt"))
	    return ">";
	else if(ent.equals("lt"))
	    return "<";
	else
	    return "&"+ent+";";
    }

    private static Element createFromTag(String tag) {
	if(isEmptyTag(tag)) {
	    tag=tag.substring(0,tag.length()-1);
	}

	String [] pieces = tag.split("\\s");
	Element result = new Element(pieces[0]);

	// OK get attributes
	for(int j=1;j<pieces.length;j++) {
	    //accumulate in currentPiece one attribute definition
	    String currentPiece=pieces[j];
	    if(currentPiece == null || currentPiece.equals(""))
		   continue; 
	    while(!isAttributeDefinition(currentPiece)) {
		j++;
		currentPiece+=" "+pieces[j];
	    }
	    
	    //now parse this attribute definition
	    int equals = currentPiece.indexOf("=");
	    String key = currentPiece.substring(0,equals);
	    String value = 
		currentPiece.substring(equals+1,currentPiece.length()).trim();
	    if(value.charAt(0)=='"') {
		value=value.substring(1,value.length()-1);
	    }
	    value=resolveEntities(value);
	    result.addAttribute(key,value);
	}
	//done getting attributes
	
	return result;
    }

    private static boolean isAttributeDefinition(String def) {
	return quoteCount(def)==2 ||
	    (quoteCount(def)==0 && containsTextAfterEquals(def));
    }

    private static boolean isEmptyTag(String tag) {
	return tag.charAt(tag.length()-1)=='/';
    }

    public static Element parseXML(String text) {
	String [] tagTextPairs = text.split("<");
	Stack stack = new Stack(); /*TOS is tag we're in, then tags that tag
				     is in, until an empty stack signifies
				     we're done*/
	Element result=null;
	for(int i=0;i<tagTextPairs.length;i++) {
	    if(tagTextPairs[i]==null || tagTextPairs[i].equals(""))
		continue;
	    String [] pieces = tagTextPairs[i].split(">");
	    // each tag and the text until the next tag
	    String tag = pieces[0];
	    String cdata = pieces.length>1?
		resolveEntities(pieces[1].trim()):
		"";
	    if(tag.charAt(0)=='/') {
		// well, it's a close tag, so we're not in "parent" anymore
		stack.pop();
	    } else if(tag.charAt(0)=='?' || tag.charAt(0)=='!') {
		// we ignore these
	    } else {
		// so we've got a new element tag
		Element element = createFromTag(tag);
		
		// add the new element to its home (which may be w/caller)
		if(stack.empty())
		    result=element;
		else
		    ((Element)stack.peek()).addChild(element);
		
		//and make sure new elements get added to correct tag
		if(!isEmptyTag(tag)) {
		    stack.push(element);
		}

		//done processing tag, now do text
		if(!cdata.equals("")) {
		    ((Element)stack.peek()).addChild(new TextNode(cdata.trim()));
		}
	    }
	}
	return result;
    }

    private static int quoteCount(String str) {	
	int res=0;
	for(int i=0;i<str.length();i++)
	    if(str.charAt(i)=='"')
		res++;
	return res;
    }

    private static boolean containsTextAfterEquals(String str) {
	String [] keyval = str.split("=");
	boolean res;
	if(keyval.length>1)
	    res=!keyval[1].equals("");
	else
	    res=false;
	return res;
    }

    
    //ACCESSING
    //accessing contents API
    public String getText() {
	Node [] children = getChildren();
	String res="";
	for(int i=0;i<children.length;i++) {
	    res+=children[i].getText();
	}
	return res;
    }

    public Node getChild(int which) {
	Node [] children = getChildren();
	return children[which];
    }

    public Node getChild(String name) {
	Node [] children = getChildren();
	for(int i=0;i<children.length;i++)
	    if(children[i].getName().equals(name))
		return children[i];
	return null;
    }
    
    public Node [] getChildren() {
	return new Node[0];
    }

    public abstract boolean isEmpty();
    
    //determine type
    public boolean isElement() {
	return false;
    }
    
    public boolean isTextNode() {
	return false;
    }
    
    //accessing metadata
    public abstract String getName();

    public String getAttribute(String name) {
	return null;
    }

    public String [] getAttributes() {
	return new String[0];
    }

    public boolean attributeExists(String name) {
	return getAttribute(name)!=null;
    }
}
